﻿using System.Globalization;
using System.Reflection;
using System.Xml.Linq;
using Phoenix.Xna.Framework.Serialization.Markup.Objects;
using Phoenix.Xna.Framework.Xml;

namespace Phoenix.Xna.Framework.Serialization.Markup.Fields
{
    /// <summary>
    /// The markup information for a reference field.
    /// </summary>
    internal class ReferenceMarkupField : MarkupFieldBase<IMarkupObject>
    {
        #region IMarkupElement Members

        /// <summary>
        /// Serialize the element to XML.
        /// </summary>
        /// <returns>The element markup serialzed as an XElement.</returns>
        public override XElement Serialize()
        {
            XName elementName = NamespaceCache.GetNamespaceFor(ObjectType) + ObjectType.Name.Replace("-", "`");
            XElement element = null;

            if (typeof(ReferenceMarkup).IsAssignableFrom(_value.GetType()))
            {
                ReferenceMarkup referenceMarkup = _value as ReferenceMarkup;
                element = new XElement(elementName, string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", AttributeIDs.ReferenceStart, referenceMarkup.ReferenceId, AttributeIDs.ReferenceEnd));
                element.SetAttribute(AttributeIDs.IsAssignableAttribute, true);
            }
            else
            {
                element = new XElement(elementName);
                element.Add(_value.Serialize());
            }

            element.SetAttribute(AttributeIDs.FieldIDAttribute, Name);
            element.SetAttribute(AttributeIDs.IsFieldAttribute, true);
            return element;
        }

        /// <summary>
        /// Deserialize the element from an XElement.
        /// </summary>
        /// <param name="element">The XElement to deserialize this element from.</param>
        public override void Deserialize(XElement element)
        {
            Name = element.ReadAttribute<string>("FieldID");
            ObjectType = NamespaceCache.GetType(element.Name.LocalName.Replace("`", "-"), element.Name.Namespace);

            if (!element.ReadAttribute<bool>(AttributeIDs.IsAssignableAttribute))
            {
                Name = element.Name.ToString();
                XElement child = (XElement)element.FirstNode;
                _value = Serializer.MarkupSelector.GetMarkupObjectFor(child);
                _value.Deserialize(child);
            }
            else
            {
                Name = element.Name.ToString();
                string referenceID = element.ReadValue<string>().Replace(AttributeIDs.ReferenceStart, string.Empty).Replace(AttributeIDs.ReferenceEnd, string.Empty);
                _value = Serializer.ReferenceManager.GetMarkupFromReferenceId(referenceID);
            }
        }

        #endregion

        #region IMarkupField Members

        /// <summary>
        /// Extract the markup information from the FieldInfo for this field.
        /// </summary>
        /// <param name="field">The FieldInfo for this field.</param>
        /// <param name="value">The value of the field.</param>
        public override void ExtractMarkup(FieldInfo field, object value)
        {
            ObjectType = field.FieldType;
            Name = field.Name;
            ShouldPersist = SerializableFieldAttribute.ShouldPersist(field, value);

            if (ShouldPersist)
            {
                _value = Serializer.MarkupSelector.GetMarkupObjectFor(value);
            }
        }

        /// <summary>
        /// Sets the dereferenced value to the instance's field.
        /// </summary>
        /// <param name="instance">The instance to set the field on.</param>
        public override void SetValue(object instance)
        {
            FieldInfo field = instance.GetType().GetField(Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);

            if (_value.Instance == null)
            {
                _value.ConstructInstance();
            }

            field.SetValue(instance, _value.Instance);
        }

        #endregion
    }
}
